package com.zhichat.infrastructure.util;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.*;
import com.google.common.collect.Lists;
import com.zhichat.infrastructure.config.OSSConfig;
import com.zhichat.infrastructure.exception.http.CommonException;
import com.zhichat.model.OSSFileMetaDataModel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.file.Path;
import java.util.Date;
import java.util.List;
import java.util.Objects;

/**
 * @ClassName: OSSClient
 * @Description: AliyunOSS 文件处理中心
 * @Author: insanetz https://space.bilibili.com/602197686
 * @CreateDate: 2024/11/16 19:27
 */
@Slf4j
public class OSSClient {

    /**
     * 定义回调 Callback 对象
     *
     * @param callbackUrl 回调地址，例如：http://1044320hcvo73.vicp.fun/message/sendFileSucCallback
     * @param keys        参数key
     * @param vals        参数val
     * @return Callback
     */
    public static Callback getCallback(String callbackUrl, String[] keys, String[] vals) {
        if (ArrayUtil.isEmpty(keys) || ArrayUtil.isEmpty(vals) || keys.length != vals.length) {
            return null;
        }
        // 上传回调参数。
        Callback callback = new Callback();
        callback.setCallbackUrl(callbackUrl);
        //（可选）设置回调请求消息头中Host的值，即您的服务器配置Host的值。
//        callback.setCallbackHost("oss-cn-hangzhou.aliyuncs.com");
        // 设置发起回调时请求body的值。
        String body = "bucket:${bucket},object:${object},"
                + "mimeType:${mimeType},size:${size},";
        for (int i = 0; i < keys.length; i++) {
            body += "" + keys[i] + ":${x:" + keys[i] + "}";
            if (i != keys.length - 1) {
                body += ",";
            }
        }
        callback.setCallbackBody(body);
        // 设置发起回调请求的Content-Type。
        callback.setCalbackBodyType(Callback.CalbackBodyType.URL);
        // 设置发起回调请求的自定义参数，由Key和Value组成，Key必须以x:开始。
        for (int i = 0; i < vals.length; i++) {
            callback.addCallbackVar(StrUtil.format("x:{}", keys[i]), vals[i]);
        }
        return callback;
    }

    /**
     * 上传文件 -> OSS
     *
     * @param file           文件
     * @param folder         文件存储包目录
     * @param customFileName 自定义文件名称（null则取上传时文件名称）
     * @param ossConfig      OSS配置容器类
     * @param callback       文件上传成功回调
     * @return success/fail
     */
    public static Boolean uploadFile(MultipartFile file, String folder, String customFileName, OSSConfig ossConfig, Callback callback) {
        String fileName = file.getOriginalFilename();
        log.info("传输文件的文件名:{}", fileName);
        if (StringUtils.hasText(customFileName)) {
            String fileSuffix = Utils.getFileSuffix(fileName);
            fileName = customFileName.concat(StrUtil.isNotBlank(fileSuffix) ? ".".concat(fileSuffix) : "");
        }
        OSS ossClient = null;
        try {
            ossClient = new OSSClientBuilder().build(ossConfig.getEndpoint(),
                    ossConfig.getAccessKeyId(),
                    ossConfig.getAccessKeySecret());
            PutObjectRequest putObjectRequest = new PutObjectRequest(ossConfig.getBucket(),
                    folder + "/" + fileName,
                    file.getInputStream());
            if (Objects.nonNull(callback)) {
                putObjectRequest.setCallback(callback);
            }
            ossClient.putObject(putObjectRequest);
//            PutObjectResult result = ossClient.putObject(putObjectRequest);
//            ResponseMessage response = result.getResponse();
//            System.out.println("response.getHeaders() - " + JSONObject.toJSONString(response.getHeaders()));
//            InputStream content = response.getContent();
//            byte[] bytes = new byte[content.available()];
//            content.read(bytes);
//            String res = new String(bytes);
//            System.out.println("response.getContent() - " + res);
//            content.close();
            log.info("上传文件成功! 文件名：{}", fileName);
            return true;
        } catch (Exception e) {
            log.error("uploadFile error", e);
            throw CommonException.FILE_OPERATE_COMMON_EXCEPTION;
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    /**
     * 上传文件 -> OSS
     *
     * @param fileStream     文件流
     * @param folder         文件存储包目录
     * @param customFileName 自定义文件名称（null则取上传时文件名称）
     * @param ossConfig      OSS配置容器类
     * @return success/fail
     */
    public static Boolean uploadFile(InputStream fileStream, String folder, String customFileName, OSSConfig ossConfig, Callback callback) {
        if (!StringUtils.hasText(customFileName)) {
            return false;
        }
        log.info("文件开始上传");
        OSS ossClient = null;
        try {
            ossClient = new OSSClientBuilder().build(ossConfig.getEndpoint(),
                    ossConfig.getAccessKeyId(),
                    ossConfig.getAccessKeySecret());
            PutObjectRequest putObjectRequest = new PutObjectRequest(ossConfig.getBucket(),
                    folder + "/" + customFileName,
                    fileStream);
            if (Objects.nonNull(callback)) {
                putObjectRequest.setCallback(callback);
            }
            ossClient.putObject(putObjectRequest);
            log.info("上传文件成功! 文件名：{}", customFileName);
            return true;
        } catch (Exception e) {
            log.error("uploadFile error", e);
            throw CommonException.FILE_OPERATE_COMMON_EXCEPTION;
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    /**
     * OSS -> 获取文件元数据
     *
     * @param folder    文件存储包目录
     * @param fileName  文件名称
     * @param ossConfig OSS配置容器类
     * @return OSSFileMetaDataModel
     */
    public static OSSFileMetaDataModel getFileMetaData(String folder, String fileName, OSSConfig ossConfig) {
        OSSFileMetaDataModel model = null;
        OSS ossClient = null;
        try {
            ossClient = new OSSClientBuilder().build(ossConfig.getEndpoint(),
                    ossConfig.getAccessKeyId(),
                    ossConfig.getAccessKeySecret());
            // 获取文件的元数据。
            ObjectMetadata metadata = ossClient.getObjectMetadata(ossConfig.getBucket(), folder + "/" + fileName);
            model = new OSSFileMetaDataModel();
            model.setFileName(fileName)
                    .setFileLength(metadata.getContentLength())
                    .setContentType(metadata.getContentType())
                    .setContentEncoding(metadata.getContentEncoding())
                    .setLastModified(metadata.getLastModified());
        } catch (Exception e) {
            log.error("getFileMetaData error", e);
            throw CommonException.FILE_OPERATE_COMMON_EXCEPTION;
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return model;
    }

    /**
     * OSS -> 获取文件临时下载链接
     *
     * @param folder    文件存储包目录
     * @param fileName  文件名称
     * @param ossConfig OSS配置容器类
     * @return 临时下载链接 List
     */
    public static List<String> getFileTmpDownloadUrl(String folder, String fileName, OSSConfig ossConfig) {
        List<String> resList = Lists.newArrayList();
        OSS ossClient = null;
        try {
            ossClient = new OSSClientBuilder().build(ossConfig.getEndpoint(),
                    ossConfig.getAccessKeyId(),
                    ossConfig.getAccessKeySecret());
            // 设置签名URL过期时间，单位为毫秒。()
            Date expiration = new Date(new Date().getTime() + 5 * 60 * 1000);
            // 生成以GET方法访问的签名URL。(没有额外请求头，其他人可以直接通过浏览器访问相关内容)
            URL url = ossClient.generatePresignedUrl(ossConfig.getBucket()
                    , folder + "/" + fileName
                    , expiration);
            resList.add(url.toString());
        } catch (Exception e) {
            log.error("getFileTmpDownloadUrl error", e);
            throw CommonException.FILE_OPERATE_COMMON_EXCEPTION;
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return resList;
    }

    /**
     * 下载文件到指定路径
     *
     * @param folder    oss文件存储包目录
     * @param fileName  oss文件名称
     * @param ossConfig OSS配置容器类
     * @param localPath 本地文件路径
     */
    public static void downloadFile2Path(String folder, String fileName, OSSConfig ossConfig, Path localPath) {
        OSS ossClient = null;
        try {
            ossClient = new OSSClientBuilder().build(ossConfig.getEndpoint(),
                    ossConfig.getAccessKeyId(),
                    ossConfig.getAccessKeySecret());
            GetObjectRequest getObjectRequest = new GetObjectRequest(ossConfig.getBucket(),
                    folder.concat("/").concat(fileName));
            ossClient.getObject(getObjectRequest, localPath.toFile());
        } catch (Exception e) {
            log.error("downloadFile2Path error", e);
            throw CommonException.FILE_OPERATE_COMMON_EXCEPTION;
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    /**
     * 流式下载文件
     *
     * @param folder    oss文件存储包目录
     * @param fileName  oss文件名称
     * @param ossConfig OSS配置容器类
     * @param response  httpServletResponse
     */
    public static void downloadFile2Stream(String folder, String fileName, OSSConfig ossConfig, HttpServletResponse response) {
        OSS ossClient = null;
        InputStream objectContent = null;
        OutputStream byteArrayOutputStream = null;
        try {
            ossClient = new OSSClientBuilder().build(ossConfig.getEndpoint(),
                    ossConfig.getAccessKeyId(),
                    ossConfig.getAccessKeySecret());
            OSSObject ossObject = ossClient.getObject(ossConfig.getBucket(), folder.concat("/").concat(fileName));
            objectContent = ossObject.getObjectContent();
            response.setContentLengthLong(ossObject.getObjectMetadata().getContentLength());
            byteArrayOutputStream = response.getOutputStream();
            byte[] readBuffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = objectContent.read(readBuffer)) != -1) {
                byteArrayOutputStream.write(readBuffer, 0, bytesRead);
            }
            byteArrayOutputStream.flush();
        } catch (Exception e) {
            log.error("downloadFile2Stream error", e);
            throw CommonException.FILE_OPERATE_COMMON_EXCEPTION;
        } finally {
            try {
                if (byteArrayOutputStream != null) {
                    byteArrayOutputStream.close();
                }
                if (objectContent != null) {
                    objectContent.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }
}
